package im.oen.boot.api.dispatch;

import im.oen.boot.api.annotation.OenApiController;
import im.oen.boot.api.annotation.handler.MethodAroundHandler;
import im.oen.boot.api.constant.OenApiConst;
import im.oen.boot.api.dao.AbsDao;
import im.oen.boot.api.dispatch.data.UriData;
import im.oen.boot.common.constant.OenCode;
import im.oen.boot.common.data.Result;
import im.oen.boot.common.exception.OenException;
import im.oen.boot.common.utils.Checker;
import im.oen.boot.common.utils.ReflectUtil;
import im.oen.boot.common.utils.Runner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Kevin.Xu
 */
public class DispatchManager {

    private Logger log = LoggerFactory.getLogger(this.getClass());
    private final ApplicationContext context;
    private final Map<String, MethodHandlerExecutor> handlers;

    public DispatchManager(ApplicationContext context) {
        this.context = context;
        this.handlers = new ConcurrentHashMap<>();
        this.init();
    }

    private void init() {
        Collection<Object> cruds = context.getBeansWithAnnotation(OenApiController.class).values();
        for (Object obj : cruds) {
            this.processCrudObj(obj);
        }
    }

    private void processCrudObj(Object obj) {
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        for (Class<?> tmp : interfaces) {
            OenApiController crud = tmp.getAnnotation(OenApiController.class);
            if (crud != null) {
                processCrudInterface(tmp, obj);
                break;
            }
        }
    }

    private void processCrudInterface(Class<?> clazz, Object obj) {
        if (!AbsDao.class.isAssignableFrom(clazz)) {
            return;
        }
        OenApiController crud = clazz.getAnnotation(OenApiController.class);
        if (crud == null) {
            return;
        }
        String uri = crud.uri().toLowerCase();
        if (Checker.isEmpty(uri)) {
            return;
        }

        Type[] types = clazz.getGenericInterfaces();
        if (types.length != 1) {
            return;
        }
        Class around = crud.around();
        MethodAroundHandler aroundHandler = (MethodAroundHandler) ReflectUtil.newObj(around);

        Class idClass = null;
        Class entityClass = null;
        Type type = types[0];
        if (type instanceof ParameterizedType) {
            entityClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
            idClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
        }
        MethodHandlerExecutor executor = new MethodHandlerExecutor(uri, idClass, entityClass, aroundHandler, (AbsDao) obj);
        this.handlers.put(uri, executor);
    }

    public UriData hasMatch(HttpServletRequest request) {
        String uri = request.getRequestURI();

        if (Checker.isEmpty(uri)) {
            return null;
        }

        String selfUri = OenApiConst.getInstance().getUri();
        if (uri.startsWith(selfUri)) {
            uri = uri.substring(selfUri.length());
        }

        String[] paths = uri.toLowerCase().split("/");
        if (Checker.isEmpty(paths)) {
            return null;
        }
        int pathLen = paths.length;
        if (pathLen < 3) {
            return null;
        }

        String className = paths[1];
        String methodName = paths[2];

        String[] params = null;
        if (pathLen > 3) {
            params = new String[pathLen - 3];
            for (int i = 0; i < params.length; i++) {
                params[i] = paths[3 + i];
            }
        }

        MethodHandlerExecutor handler = this.handlers.get(className);
        if (handler == null) {
            return null;
        }

        return new UriData(className, methodName, handler, params);

    }

    public void doService(HttpServletRequest request, HttpServletResponse response, UriData data) {
        try {
            String body = this.readPostData(request);
            if (Checker.isEmpty((body))) {
                body = "{}";
            }
            Object res = this.exec(data, body);
            this.write(response, HttpStatus.OK, Result.of(res));
        } catch (OenException e) {
            log.error(e.getMessage(), e);
            this.write(response, HttpStatus.INTERNAL_SERVER_ERROR, Result.of(e.getErrorCode(), e.getMessage()));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            this.write(response, HttpStatus.INTERNAL_SERVER_ERROR, Result.of(OenCode.SERVER_ERROR, e.getMessage()));
        }
    }

    private void write(final HttpServletResponse response, final HttpStatus status, final Result content) {
        Runner.safeRun(() -> {
            response.setHeader("Content-Type", "application/json;charset=UTF-8");
            response.setStatus(status.value());
            response.getWriter().write(content.toJson());
        });
    }


    private Object exec(UriData data, String body) {
        if (Checker.isEmpty(data) || data.isEmpty()) {
            throw OenCode.NOT_FOUND.exception();
        }
        return data.exec(body);
    }

    private String readPostData(HttpServletRequest request) {
        try {
            StringBuilder result = new StringBuilder();
            String line;
            BufferedReader reader = request.getReader();
            while (null != (line = reader.readLine())) {
                result.append(line);
            }
            return result.toString();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }
}
